#include "pfm.h"
#include "test_util.h"

using namespace std;

int RBFTest_1(PagedFileManager &pfm) {
    // Functions Tested:
    // 1. Create File
    std::cout << std::endl << "***** In RBF Test Case 01 *****" << std::endl;

    RC rc;
    std::string fileName = "test1";

    // Create a file named "test"
    rc = pfm.createFile(fileName);
    assert(rc == success && "Creating the file failed.");

    rc = createFileShouldSucceed(fileName);
    assert(rc == success && "Creating the file failed.");

    // Create "test" again, should fail
    rc = pfm.createFile(fileName);
    assert(rc != success && "Creating the same file should fail.");

    std::cout << "RBF Test Case 01 Finished! The result will be examined." << std::endl << std::endl;
    return 0;
}

int RBFTest_2(PagedFileManager &pfm) {
    // Functions Tested:
    // 1. Destroy File
    std::cout << std::endl << "***** In RBF Test Case 02 *****" << std::endl;

    RC rc;
    string fileName = "test1";

    rc = pfm.destroyFile(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    rc = destroyFileShouldSucceed(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    // Destroy "test1" again, should fail
    rc = pfm.destroyFile(fileName);
    assert(rc != success && "Destroy the same file should fail.");

    std::cout << "RBF Test Case 02 Finished! The result will be examined." << std::endl << std::endl;
    return 0;
}

int RBFTest_3(PagedFileManager &pfm) {
    // Functions Tested:
    // 1. Create File
    // 2. Open File
    // 3. Get Number Of Pages
    // 4. Close File
    std::cout << std::endl << "***** In RBF Test Case 03 *****" << std::endl;

    RC rc;
    std::string fileName = "test3";

    // Create a file named "test3"
    rc = pfm.createFile(fileName);
    assert(rc == success && "Creating the file should not fail.");

    rc = createFileShouldSucceed(fileName);
    assert(rc == success && "Creating the file should not fail.");

    // Open the file
    FileHandle fileHandle;
    rc = pfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    // Get the number of pages in the test file. In this case, it should be zero.
    unsigned count = fileHandle.getNumberOfPages();
    assert(count == (unsigned) 0 && "The page count should be zero at this moment.");

    // Close the file
    rc = pfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    std::cout << "RBF Test Case 03 Finished! The result will be examined." << std::endl << std::endl;

    return 0;
}

int RBFTest_4(PagedFileManager &pfm) {
    // Functions Tested:
    // 1. Open File
    // 2. Append Page
    // 3. Get Number Of Pages
    // 4. Get Counter Values
    // 5. Close File
    std::cout << std::endl << "***** In RBF Test Case 04 *****" << std::endl;

    RC rc;
    std::string fileName = "test3";

    unsigned readPageCount = 0;
    unsigned writePageCount = 0;
    unsigned appendPageCount = 0;
    unsigned readPageCount1 = 0;
    unsigned writePageCount1 = 0;
    unsigned appendPageCount1 = 0;

    // Open the file "test3"
    FileHandle fileHandle;
    rc = pfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    // Collect before counters
    rc = fileHandle.collectCounterValues(readPageCount, writePageCount, appendPageCount);
    if (rc != success) {
        std::cout << "[FAIL] collectCounterValues() failed. Test Case 4 failed." << std::endl;
        pfm.closeFile(fileHandle);
        return -1;
    }

    // Append the first page
    void *data = malloc(PAGE_SIZE);
    for (unsigned i = 0; i < PAGE_SIZE; i++) {
        *((char *) data + i) = i % 96 + 30;
    }
    rc = fileHandle.appendPage(data);
    assert(rc == success && "Appending a page should not fail.");

    // collect after counters
    rc = fileHandle.collectCounterValues(readPageCount1, writePageCount1, appendPageCount1);
    if (rc != success) {
        std::cout << "[FAIL] collectCounterValues() failed. Test Case 4 failed." << std::endl;
        pfm.closeFile(fileHandle);
        return -1;
    }
    std::cout << "before:R W A - " << readPageCount << " " << writePageCount << " " << appendPageCount
              << " after:R W A - "
              << readPageCount1 << " " << writePageCount1 << " " << appendPageCount1 << std::endl;
    assert(appendPageCount1 > appendPageCount && "The appendPageCount should have been increased.");

    // Get the number of pages
    unsigned count = fileHandle.getNumberOfPages();
    assert(count == (unsigned) 1 && "The count should be one at this moment.");

    // Close the file "test3"
    rc = pfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    free(data);

    std::cout << "RBF Test Case 04 Finished! The result will be examined." << std::endl << std::endl;

    return 0;
}

int RBFTest_5(PagedFileManager &pfm) {
    // Functions Tested:
    // 1. Open File
    // 2. Read Page
    // 3. Close File
    std::cout << std::endl << "***** In RBF Test Case 05 *****" << std::endl;

    RC rc;
    std::string fileName = "test3";

    unsigned readPageCount = 0;
    unsigned writePageCount = 0;
    unsigned appendPageCount = 0;
    unsigned readPageCount1 = 0;
    unsigned writePageCount1 = 0;
    unsigned appendPageCount1 = 0;

    // Open the file "test3"
    FileHandle fileHandle;
    rc = pfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    // collect before counters
    rc = fileHandle.collectCounterValues(readPageCount, writePageCount, appendPageCount);
    if (rc != success) {
        std::cout << "[FAIL] collectCounterValues() failed. Test Case 5 failed." << std::endl;
        pfm.closeFile(fileHandle);
        return -1;
    }

    // Read the first page
    void *buffer = malloc(PAGE_SIZE);
    rc = fileHandle.readPage(0, buffer);
    assert(rc == success && "Reading a page should not fail.");

    // collect after counters
    rc = fileHandle.collectCounterValues(readPageCount1, writePageCount1, appendPageCount1);
    if (rc != success) {
        std::cout << "[FAIL] collectCounterValues() failed. Test Case 5 failed." << std::endl;
        pfm.closeFile(fileHandle);
        return -1;
    }
    std::cout << "before:R W A - " << readPageCount << " " << writePageCount << " " << appendPageCount
              << " after:R W A - "
              << readPageCount1 << " " << writePageCount1 << " " << appendPageCount1 << std::endl;
    assert(readPageCount1 > readPageCount && "The readPageCount should have been increased.");

    // Check the integrity of the page
    void *data = malloc(PAGE_SIZE);
    for (unsigned i = 0; i < PAGE_SIZE; i++) {
        *((char *) data + i) = i % 96 + 30;
    }
    rc = memcmp(data, buffer, PAGE_SIZE);
    assert(rc == success && "Checking the integrity of the page should not fail.");

    // Read a non-existing page
    rc = fileHandle.readPage(1, buffer);
    assert(rc != success && "Reading a non-existing page should fail.");

    // Close the file "test3"
    rc = pfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    free(data);
    free(buffer);

    std::cout << "RBF Test Case 05 Finished! The result will be examined." << std::endl << std::endl;

    return 0;
}

int RBFTest_6(PagedFileManager &pfm) {
    // Functions Tested:
    // 1. Open File
    // 2. Write Page
    // 3. Read Page
    // 4. Close File
    // 5. Destroy File
    std::cout << std::endl << "***** In RBF Test Case 06 *****" << std::endl;

    RC rc;
    std::string fileName = "test3";

    unsigned readPageCount = 0;
    unsigned writePageCount = 0;
    unsigned appendPageCount = 0;
    unsigned readPageCount1 = 0;
    unsigned writePageCount1 = 0;
    unsigned appendPageCount1 = 0;

    // Open the file "test3"
    FileHandle fileHandle;
    rc = pfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    // collect before counters
    rc = fileHandle.collectCounterValues(readPageCount, writePageCount, appendPageCount);
    if (rc != success) {
        std::cout << "[FAIL] collectCounterValues() failed. Test Case 6 failed." << std::endl;
        pfm.closeFile(fileHandle);
        return -1;
    }

    // Update the first page
    void *data = malloc(PAGE_SIZE);
    for (unsigned i = 0; i < PAGE_SIZE; i++) {
        *((char *) data + i) = i % 10 + 30;
    }
    rc = fileHandle.writePage(0, data);
    assert(rc == success && "Writing a page should not fail.");

    // Read the page
    void *buffer = malloc(PAGE_SIZE);
    rc = fileHandle.readPage(0, buffer);
    assert(rc == success && "Reading a page should not fail.");

    // collect after counters
    rc = fileHandle.collectCounterValues(readPageCount1, writePageCount1, appendPageCount1);
    if (rc != success) {
        std::cout << "[FAIL] collectCounterValues() failed. Test Case 6 failed." << std::endl;
        pfm.closeFile(fileHandle);
        return -1;
    }
    std::cout << "before:R W A - " << readPageCount << " " << writePageCount << " " << appendPageCount
              << " after:R W A - "
              << readPageCount1 << " " << writePageCount1 << " " << appendPageCount1 << std::endl;
    assert(writePageCount1 > writePageCount && "The writePageCount should have been increased.");
    assert(readPageCount1 > readPageCount && "The readPageCount should have been increased.");

    // Check the integrity
    rc = memcmp(data, buffer, PAGE_SIZE);
    assert(rc == success && "Checking the integrity of a page should not fail.");

    // Write a non-existing page
    rc = fileHandle.writePage(1, buffer);
    assert(rc != success && "Writing a non-existing page should fail.");

    // Close the file "test3"
    rc = pfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    free(data);
    free(buffer);

    // Destroy the file
    rc = pfm.destroyFile(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    rc = destroyFileShouldSucceed(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    std::cout << "RBF Test Case 06 Finished! The result will be examined." << std::endl << std::endl;

    return 0;

}

int RBFTest_7(PagedFileManager &pfm) {
    // Functions Tested:
    // 1. Create File
    // 2. Open File
    // 3. Append Page
    // 4. Get Number Of Pages
    // 5. Read Page
    // 6. Write Page
    // 7. Close File
    // 8. Destroy File
    std::cout << std::endl << "***** In RBF Test Case 07 *****" << std::endl;

    RC rc;
    std::string fileName = "test7";

    unsigned readPageCount = 0;
    unsigned writePageCount = 0;
    unsigned appendPageCount = 0;
    unsigned readPageCount1 = 0;
    unsigned writePageCount1 = 0;
    unsigned appendPageCount1 = 0;

    // Create the file named "test7"
    rc = pfm.createFile(fileName);
    assert(rc == success && "Creating the file should not fail.");

    rc = createFileShouldSucceed(fileName);
    assert(rc == success && "Creating the file failed.");

    // Open the file "test7"
    FileHandle fileHandle;
    rc = pfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    // collect before counters
    rc = fileHandle.collectCounterValues(readPageCount, writePageCount, appendPageCount);
    if (rc != success) {
        std::cout << "[FAIL] collectCounterValues() failed. Test Case 7 failed." << std::endl;
        pfm.closeFile(fileHandle);
        return -1;
    }

    // Append 100 pages
    void *data = malloc(PAGE_SIZE);
    for (unsigned j = 0; j < 100; j++) {
        for (unsigned i = 0; i < PAGE_SIZE; i++) {
            *((char *) data + i) = i % (j + 1) + 30;
        }
        rc = fileHandle.appendPage(data);
        assert(rc == success && "Appending a page should not fail.");
    }
    std::cout << "100 Pages have been successfully appended!" << std::endl;

    // collect after counters
    rc = fileHandle.collectCounterValues(readPageCount1, writePageCount1, appendPageCount1);
    if (rc != success) {
        std::cout << "[FAIL] collectCounterValues() failed. Test Case 7 failed." << std::endl;
        pfm.closeFile(fileHandle);
        return -1;
    }
    std::cout << "before:R W A - " << readPageCount << " " << writePageCount << " " << appendPageCount
              << " after:R W A - "
              << readPageCount1 << " " << writePageCount1 << " " << appendPageCount1 << std::endl;
    assert(appendPageCount1 > appendPageCount && "The appendPageCount should have been increased.");

    // Get the number of pages
    unsigned count = fileHandle.getNumberOfPages();
    assert(count == (unsigned) 100 && "The count should be 100 at this moment.");

    // Read the 87th page and check integrity
    void *buffer = malloc(PAGE_SIZE);
    rc = fileHandle.readPage(86, buffer);
    assert(rc == success && "Reading a page should not fail.");

    for (unsigned i = 0; i < PAGE_SIZE; i++) {
        *((char *) data + i) = i % 87 + 30;
    }
    rc = memcmp(buffer, data, PAGE_SIZE);
    assert(rc == success && "Checking the integrity of a page should not fail.");
    std::cout << "The data in 87th page is correct!" << std::endl;

    // Update the 87th page
    for (unsigned i = 0; i < PAGE_SIZE; i++) {
        *((char *) data + i) = i % 60 + 30;
    }
    rc = fileHandle.writePage(86, data);
    assert(rc == success && "Writing a page should not fail.");

    // Read the 87th page and check integrity
    rc = fileHandle.readPage(86, buffer);
    assert(rc == success && "Reading a page should not fail.");

    rc = memcmp(buffer, data, PAGE_SIZE);
    assert(rc == success && "Checking the integrity of a page should not fail.");

    // Close the file "test7"
    rc = pfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    // Destroy the file
    rc = pfm.destroyFile(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    rc = destroyFileShouldSucceed(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    free(data);
    free(buffer);

    std::cout << "RBF Test Case 07 Finished! The result will be examined." << std::endl << std::endl;

    return 0;
}

int RBFTest_8(RecordBasedFileManager &rbfm) {
    // Functions tested
    // 1. Create Record-Based File
    // 2. Open Record-Based File
    // 3. Insert Record
    // 4. Read Record
    // 5. Close Record-Based File
    // 6. Destroy Record-Based File
    std::cout << std::endl << "***** In RBF Test Case 08 *****" << std::endl;

    RC rc;
    std::string fileName = "test8";

    // Create a file named "test8"
    rc = rbfm.createFile(fileName);
    assert(rc == success && "Creating the file should not fail.");

    rc = createFileShouldSucceed(fileName);
    assert(rc == success && "Creating the file should not fail.");

    // Open the file "test8"
    FileHandle fileHandle;
    rc = rbfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    RID rid;
    int recordSize = 0;
    void *record = malloc(100);
    void *returnedData = malloc(100);

    std::vector<Attribute> recordDescriptor;
    createRecordDescriptor(recordDescriptor);

    // Initialize a NULL field indicator
    int nullFieldsIndicatorActualSize = getActualByteForNullsIndicator(recordDescriptor.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullFieldsIndicatorActualSize);
    memset(nullsIndicator, 0, nullFieldsIndicatorActualSize);

    // Insert a record into a file and print the record
    prepareRecord(recordDescriptor.size(), nullsIndicator, 8, "Anteater", 25, 177.8, 6200, record, &recordSize);
    std::cout << std::endl << "Inserting Data:" << std::endl;
    rbfm.printRecord(recordDescriptor, record);

    rc = rbfm.insertRecord(fileHandle, recordDescriptor, record, rid);
    assert(rc == success && "Inserting a record should not fail.");

    // Given the rid, read the record from file
    rc = rbfm.readRecord(fileHandle, recordDescriptor, rid, returnedData);
    assert(rc == success && "Reading a record should not fail.");

    std::cout << std::endl << "Returned Data:" << std::endl;
    rbfm.printRecord(recordDescriptor, returnedData);

    // Compare whether the two memory blocks are the same
    if (memcmp(record, returnedData, recordSize) != 0) {
        std::cout << "[FAIL] Test Case 8 Failed!" << std::endl << std::endl;
        free(nullsIndicator);
        free(record);
        free(returnedData);
        return -1;
    }

    std::cout << std::endl;

    // Close the file "test8"
    rc = rbfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    // Destroy the file
    rc = rbfm.destroyFile(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    rc = destroyFileShouldSucceed(fileName);
    assert(rc == success && "Destroying the file should not fail.");
    free(nullsIndicator);
    free(record);
    free(returnedData);

    std::cout << "RBF Test Case 08 Finished! The result will be examined." << std::endl << std::endl;

    return 0;
}

int RBFTest_8b(RecordBasedFileManager &rbfm) {
    // Functions tested
    // 1. Create Record-Based File
    // 2. Open Record-Based File
    // 3. Insert Record - NULL
    // 4. Read Record
    // 5. Close Record-Based File
    // 6. Destroy Record-Based File
    std::cout << std::endl << "***** In RBF Test Case 08b *****" << std::endl;

    RC rc;
    std::string fileName = "test8b";

    // Create a file named "test8b"
    rc = rbfm.createFile(fileName);
    assert(rc == success && "Creating the file should not fail.");

    rc = createFileShouldSucceed(fileName);
    assert(rc == success && "Creating the file failed.");

    // Open the file "test8b"
    FileHandle fileHandle;
    rc = rbfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    RID rid;
    int recordSize = 0;
    void *record = malloc(100);
    void *returnedData = malloc(100);

    std::vector<Attribute> recordDescriptor;
    createRecordDescriptor(recordDescriptor);

    // NULL field indicator
    int nullFieldsIndicatorActualSize = getActualByteForNullsIndicator(recordDescriptor.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullFieldsIndicatorActualSize);
    memset(nullsIndicator, 0, nullFieldsIndicatorActualSize);

    // Setting the age & salary fields value as null
    nullsIndicator[0] = 80; // 01010000

    // Insert a record into a file
    prepareRecord(recordDescriptor.size(), nullsIndicator, 8, "Anteater", NULL, 177.8, NULL, record, &recordSize);
    std::cout << std::endl << "Inserting Data:" << std::endl;
    rbfm.printRecord(recordDescriptor, record);

    rc = rbfm.insertRecord(fileHandle, recordDescriptor, record, rid);
    assert(rc == success && "Inserting a record should not fail.");

    // Given the rid, read the record from file
    rc = rbfm.readRecord(fileHandle, recordDescriptor, rid, returnedData);
    assert(rc == success && "Reading a record should not fail.");

    // The salary field should not be printed
    std::cout << std::endl << "Returned Data:" << std::endl;
    rbfm.printRecord(recordDescriptor, returnedData);

    // Compare whether the two memory blocks are the same
    if (memcmp(record, returnedData, recordSize) != 0) {
        std::cout << "[FAIL] Test Case 8b Failed!" << std::endl << std::endl;
        free(nullsIndicator);
        free(record);
        free(returnedData);
        return -1;
    }

    std::cout << std::endl;

    // Close the file "test8b"
    rc = rbfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    // Destroy File
    rc = rbfm.destroyFile(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    rc = destroyFileShouldSucceed(fileName);
    assert(rc == success && "Destroying the file should not fail.");
    free(nullsIndicator);
    free(record);
    free(returnedData);

    std::cout << "RBF Test Case 08b Finished! The result will be examined." << std::endl << std::endl;

    return 0;
}

int RBFTest_9(RecordBasedFileManager &rbfm, std::vector<RID> &rids, std::vector<int> &sizes) {
    // Functions tested
    // 1. Create Record-Based File
    // 2. Open Record-Based File
    // 3. Insert Multiple Records
    // 4. Close Record-Based File
    std::cout << std::endl << "***** In RBF Test Case 09 *****" << std::endl;

    RC rc;
    std::string fileName = "test9";

    // Create a file named "test9"
    rc = rbfm.createFile(fileName);
    assert(rc == success && "Creating the file should not fail.");

    rc = createFileShouldSucceed(fileName);
    assert(rc == success && "Creating the file failed.");

    // Open the file "test9"
    FileHandle fileHandle;
    rc = rbfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    RID rid;
    void *record = malloc(1000);
    int numRecords = 2000;

    std::vector<Attribute> recordDescriptor;
    createLargeRecordDescriptor(recordDescriptor);

    for (Attribute &i : recordDescriptor) {
        std::cout << "Attr Name: " << i.name << " Attr Type: " << (AttrType) i.type
                  << " Attr Len: " << i.length << std::endl;
    }
    std::cout << std::endl;

    // NULL field indicator
    int nullFieldsIndicatorActualSize = getActualByteForNullsIndicator(recordDescriptor.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullFieldsIndicatorActualSize);
    memset(nullsIndicator, 0, nullFieldsIndicatorActualSize);

    // Insert 2000 records into file
    for (int i = 0; i < numRecords; i++) {
        // Test insert Record
        int size = 0;
        memset(record, 0, 1000);
        prepareLargeRecord(recordDescriptor.size(), nullsIndicator, i, record, &size);

        rc = rbfm.insertRecord(fileHandle, recordDescriptor, record, rid);
        assert(rc == success && "Inserting a record should not fail.");

        rids.push_back(rid);
        sizes.push_back(size);
    }
    // Close the file "test9"
    rc = rbfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    free(record);


    // Write RIDs to the disk. Do not use this code in your codebase. This is not a PAGE-BASED operation - for the test purpose only.
    std::ofstream ridsFile("test9rids", std::ios::out | std::ios::trunc | std::ios::binary);

    if (ridsFile.is_open()) {
        ridsFile.seekp(0, std::ios::beg);
        for (int i = 0; i < numRecords; i++) {
            ridsFile.write(reinterpret_cast<const char *>(&rids[i].pageNum),
                           sizeof(unsigned));
            ridsFile.write(reinterpret_cast<const char *>(&rids[i].slotNum),
                           sizeof(unsigned));
            if (i % 1000 == 0) {
                std::cout << "RID #" << i << ": " << rids[i].pageNum << ", "
                          << rids[i].slotNum << std::endl;
            }
        }
        ridsFile.close();
    }

    // Write sizes vector to the disk. Do not use this code in your codebase. This is not a PAGE-BASED operation - for the test purpose only.
    std::ofstream sizesFile("test9sizes", std::ios::out | std::ios::trunc | std::ios::binary);

    if (sizesFile.is_open()) {
        sizesFile.seekp(0, std::ios::beg);
        for (int i = 0; i < numRecords; i++) {
            sizesFile.write(reinterpret_cast<const char *>(&sizes[i]), sizeof(int));
            if (i % 1000 == 0) {
                std::cout << "Sizes #" << i << ": " << sizes[i] << std::endl;
            }
        }
        sizesFile.close();
    }

    std::cout << "RBF Test Case 09 Finished! The result will be examined." << std::endl << std::endl;
    free(nullsIndicator);
    return 0;
}

int RBFTest_10(RecordBasedFileManager &rbfm) {
    // Functions tested
    // 1. Open Record-Based File
    // 2. Read Multiple Records
    // 3. Close Record-Based File
    // 4. Destroy Record-Based File
    std::cout << std::endl << "***** In RBF Test Case 10 *****" << std::endl;

    RC rc;
    std::string fileName = "test9";

    // Open the file "test9"
    FileHandle fileHandle;
    rc = rbfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    int numRecords = 2000;
    void *record = malloc(1000);
    void *returnedData = malloc(1000);

    std::vector<Attribute> recordDescriptor;
    createLargeRecordDescriptor(recordDescriptor);

    std::vector<RID> rids;
    std::vector<int> sizes;
    RID tempRID;

    // Read rids from the disk - do not use this code in your codebase. This is not a PAGE-BASED operation - for the test purpose only.
    std::ifstream ridsFileRead("test9rids", std::ios::in | std::ios::binary);

    unsigned pageNum;
    unsigned slotNum;

    if (ridsFileRead.is_open()) {
        ridsFileRead.seekg(0, std::ios::beg);
        for (int i = 0; i < numRecords; i++) {
            ridsFileRead.read(reinterpret_cast<char *>(&pageNum), sizeof(unsigned));
            ridsFileRead.read(reinterpret_cast<char *>(&slotNum), sizeof(unsigned));
            if (i % 1000 == 0) {
                std::cout << "loaded RID #" << i << ": " << pageNum << ", " << slotNum << std::endl;
            }
            tempRID.pageNum = pageNum;
            tempRID.slotNum = slotNum;
            rids.push_back(tempRID);
        }
        ridsFileRead.close();
    }

    assert(rids.size() == (unsigned) numRecords && "Reading records should not fail.");

    // Read sizes vector from the disk - do not use this code in your codebase. This is not a PAGE-BASED operation - for the test purpose only.
    std::ifstream sizesFileRead("test9sizes", std::ios::in | std::ios::binary);

    int tempSize;

    if (sizesFileRead.is_open()) {
        sizesFileRead.seekg(0, std::ios::beg);
        for (int i = 0; i < numRecords; i++) {
            sizesFileRead.read(reinterpret_cast<char *>(&tempSize), sizeof(int));
            if (i % 1000 == 0) {
                std::cout << "loaded Sizes #" << i << ": " << tempSize << std::endl;
            }
            sizes.push_back(tempSize);
        }
        sizesFileRead.close();
    }

    assert(sizes.size() == (unsigned) numRecords && "Reading records should not fail.");

    // NULL field indicator
    int nullFieldsIndicatorActualSize = getActualByteForNullsIndicator(recordDescriptor.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullFieldsIndicatorActualSize);
    memset(nullsIndicator, 0, nullFieldsIndicatorActualSize);

    for (int i = 0; i < numRecords; i++) {
        memset(record, 0, 1000);
        memset(returnedData, 0, 1000);
        rc = rbfm.readRecord(fileHandle, recordDescriptor, rids[i], returnedData);
        assert(rc == success && "Reading a record should not fail.");

        if (i % 1000 == 0) {
            std::cout << std::endl << "Returned Data:" << std::endl;
            rbfm.printRecord(recordDescriptor, returnedData);
        }

        int size = 0;
        prepareLargeRecord(recordDescriptor.size(), nullsIndicator, i, record, &size);
        if (memcmp(returnedData, record, sizes[i]) != 0) {
            std::cout << "[FAIL] Test Case 10 Failed!" << std::endl << std::endl;
            free(nullsIndicator);
            free(record);
            free(returnedData);
            return -1;
        }
    }

    std::cout << std::endl;

    // Close the file "test9"
    rc = rbfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    rc = rbfm.destroyFile(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    rc = destroyFileShouldSucceed(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    free(nullsIndicator);
    free(record);
    free(returnedData);

    std::cout << "RBF Test Case 10 Finished! The result will be examined." << std::endl << std::endl;

    remove("test9sizes");
    remove("test9rids");

    return 0;
}

int RBFTest_11(RecordBasedFileManager &rbfm) {
    // Functions tested
    // 1. Create Record-Based File
    // 2. Open Record-Based File
    // 3. Insert Multiple Records
    // 4. Close Record-Based File
    std::cout << std::endl << "***** In RBF Test Case 11 *****" << std::endl;

    RC rc;
    std::string fileName = "test11";

    // Create a file named "test11"
    rc = rbfm.createFile(fileName);
    assert(rc == success && "Creating the file should not fail.");

    rc = createFileShouldSucceed(fileName);
    assert(rc == success && "Creating the file should not fail.");

    // Open the file "test11"
    FileHandle fileHandle;
    rc = rbfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    RID rid;
    void *record = malloc(1000);
    void *returnedData = malloc(1000);
    int numRecords = 10000;

    std::vector <Attribute> recordDescriptor;
    createLargeRecordDescriptor2(recordDescriptor);

    for (Attribute &i : recordDescriptor) {
        std::cout << "Attr Name: " << i.name << " Attr Type: " << (AttrType) i.type
                  << " Attr Len: " << i.length << std::endl;
    }

    // NULL field indicator
    int nullFieldsIndicatorActualSize = getActualByteForNullsIndicator(recordDescriptor.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullFieldsIndicatorActualSize);
    memset(nullsIndicator, 0, nullFieldsIndicatorActualSize);

    std::vector<RID> rids;
    // Insert 10000 records into file
    for (int i = 0; i < numRecords; i++) {
        // Test insert Record
        memset(record, 0, 1000);
        int size = 0;
        prepareLargeRecord2(recordDescriptor.size(), nullsIndicator, i, record, &size);

        rc = rbfm.insertRecord(fileHandle, recordDescriptor, record, rid);
        assert(rc == success && "Inserting a record should not fail.");

        rids.push_back(rid);
    }

    // Close the file
    rc = rbfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    free(record);
    free(returnedData);

    assert(rids.size() == (unsigned) numRecords && "Inserting records should not fail.");

    // Write RIDs to the disk. Do not use this code in your codebase. This is not a page-based operation - for the test purpose only.
    std::ofstream ridsFile("test11rids", std::ios::out | std::ios::trunc | std::ios::binary);

    if (ridsFile.is_open()) {
        ridsFile.seekp(0, std::ios::beg);
        for (int i = 0; i < numRecords; i++) {
            ridsFile.write(reinterpret_cast<const char *>(&rids[i].pageNum),
                           sizeof(unsigned));
            ridsFile.write(reinterpret_cast<const char *>(&rids[i].slotNum),
                           sizeof(unsigned));
            if (i % 1000 == 0) {
                std::cout << "RID #" << i << ": " << rids[i].pageNum << ", "
                          << rids[i].slotNum << std::endl;
            }
        }
        ridsFile.close();
    }

    std::cout << "RBF Test Case 11 Finished! The result will be examined." << std::endl << std::endl;
    free(nullsIndicator);
    return 0;
}

int RBFTest_12(RecordBasedFileManager &rbfm) {
    // Functions tested
    // 1. Open Record-Based File
    // 2. Read Multiple Records
    // 3. Close Record-Based File
    std::cout << std::endl << "***** In RBF Test Case 12 *****" << std::endl;

    RC rc;
    std::string fileName = "test11";

    // Open the file "test11"
    FileHandle fileHandle;
    rc = rbfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    void *record = malloc(1000);
    void *returnedData = malloc(1000);
    int numRecords = 10000;

    std::vector<Attribute> recordDescriptor;
    createLargeRecordDescriptor2(recordDescriptor);

    for (Attribute &i : recordDescriptor) {
        std::cout << "Attr Name: " << i.name << " Attr Type: " << (AttrType) i.type
                  << " Attr Len: " << i.length << std::endl;
    }

    std::vector<RID> rids;
    RID tempRID;

    // Read rids from the disk - do not use this code. This is not a page-based operation. For test purpose only.
    std::ifstream ridsFileRead("test11rids", std::ios::in | std::ios::binary);

    unsigned pageNum;
    unsigned slotNum;

    if (ridsFileRead.is_open()) {
        ridsFileRead.seekg(0, std::ios::beg);
        for (int i = 0; i < numRecords; i++) {
            ridsFileRead.read(reinterpret_cast<char *>(&pageNum), sizeof(unsigned));
            ridsFileRead.read(reinterpret_cast<char *>(&slotNum), sizeof(unsigned));
            if (i % 1000 == 0) {
                std::cout << "loaded RID #" << i << ": " << pageNum << ", " << slotNum << std::endl;
            }
            tempRID.pageNum = pageNum;
            tempRID.slotNum = slotNum;
            rids.push_back(tempRID);
        }
        ridsFileRead.close();
    }

    assert(rids.size() == (unsigned) numRecords && "Reading records should not fail.");

    // NULL field indicator
    int nullFieldsIndicatorActualSize = getActualByteForNullsIndicator(recordDescriptor.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullFieldsIndicatorActualSize);
    memset(nullsIndicator, 0, nullFieldsIndicatorActualSize);

    // Compare records from the disk read with the record created from the method
    for (int i = 0; i < numRecords; i++) {
        memset(record, 0, 1000);
        memset(returnedData, 0, 1000);
        rc = rbfm.readRecord(fileHandle, recordDescriptor, rids[i],
                             returnedData);
        if (rc != success) {
            return -1;
        }
        assert(rc == success);

        int size = 0;
        prepareLargeRecord2(recordDescriptor.size(), nullsIndicator, i, record, &size);
        if (memcmp(returnedData, record, size) != 0) {
            std::cout << "Test Case 12 Failed!" << std::endl << std::endl;
            free(record);
            free(returnedData);
            return -1;
        }
    }


    // Close the file
    rc = rbfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    free(record);
    free(returnedData);

    std::cout << "RBF Test Case 12 Finished! The result will be examined." << std::endl << std::endl;

    free(nullsIndicator);
    return 0;
}

int RBFTest_Delete(RecordBasedFileManager &rbfm) {
    // Functions tested
    // 1. Create Record-Based File
    // 2. Open Record-Based File
    // 3. Insert Record (3)
    // 4. Delete Record (1)
    // 5. Read Record
    // 6. Close Record-Based File
    // 7. Destroy Record-Based File
    std::cout << std::endl << "***** In RBF Test Case Delete *****" << std::endl;

    RC rc;
    std::string fileName = "test_delete";

    rc = rbfm.createFile(fileName);
    assert(rc == success && "Creating the file should not fail.");

    rc = createFileShouldSucceed(fileName);
    assert(rc == success && "Creating the file should not fail.");

    // Open the file
    FileHandle fileHandle;
    rc = rbfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    RID rid;
    int recordSize = 0;
    void *record = malloc(100);
    void *returnedData = malloc(100);

    std::vector<Attribute> recordDescriptor;
    createRecordDescriptor(recordDescriptor);

    // Initialize a NULL field indicator
    int nullFieldsIndicatorActualSize = getActualByteForNullsIndicator(recordDescriptor.size());
    auto *nullsIndicator = (unsigned char *) malloc(nullFieldsIndicatorActualSize);
    memset(nullsIndicator, 0, nullFieldsIndicatorActualSize);

    // Insert a record into a file and print the record
    prepareRecord(recordDescriptor.size(), nullsIndicator, 8, "Testcase", 25, 177.8, 6200, record,
                  &recordSize);
    std::cout << std::endl << "Inserting Data:" << std::endl;
    rbfm.printRecord(recordDescriptor, record);

    rc = rbfm.insertRecord(fileHandle, recordDescriptor, record, rid);
    assert(rc == success && "Inserting a record should not fail.");
    // save the returned RID
    RID rid0 = rid;
    std::cout << std::endl;

    memset(nullsIndicator, 0, nullFieldsIndicatorActualSize);

    // Insert a record into a file and print the record

    nullsIndicator[0] = 128;
    prepareRecord(recordDescriptor.size(), nullsIndicator, 0, "", 25, 177.8, 6200, record,
                  &recordSize);
    std::cout << std::endl << "Inserting Data:" << std::endl;
    rbfm.printRecord(recordDescriptor, record);

    rc = rbfm.insertRecord(fileHandle, recordDescriptor, record, rid);
    assert(rc == success && "Inserting a record should not fail.");
    // save the returned RID
    RID rid1 = rid;

    std::cout << std::endl << "Inserting Data:" << std::endl;
    rbfm.printRecord(recordDescriptor, record);

    rc = rbfm.insertRecord(fileHandle, recordDescriptor, record, rid);
    assert(rc == success && "Inserting a record should not fail.");

    std::cout << std::endl << "Inserting Data:" << std::endl;
    rbfm.printRecord(recordDescriptor, record);

    rc = rbfm.insertRecord(fileHandle, recordDescriptor, record, rid);

    assert(rc == success && "Inserting a record should not fail.");

    rc = rbfm.deleteRecord(fileHandle, recordDescriptor, rid0);
    assert(rc == success && "Deleting a record should not fail.");

    rc = rbfm.readRecord(fileHandle, recordDescriptor, rid0, returnedData);
    assert(rc != success && "Reading a deleted record should fail.");

    // Given the rid, read the record from file
    rc = rbfm.readRecord(fileHandle, recordDescriptor, rid1, returnedData);
    assert(rc == success && "Reading a record should not fail.");

    std::cout << std::endl << "Returned Data:" << std::endl;
    rbfm.printRecord(recordDescriptor, returnedData);

    // Compare whether the two memory blocks are the same
    if (memcmp(record, returnedData, recordSize) != 0) {
        std::cout << "[FAIL] Test Case Delete Failed!" << std::endl << std::endl;
        free(record);
        free(returnedData);
        free(nullsIndicator);
        return -1;
    }

    rc = rbfm.insertRecord(fileHandle, recordDescriptor, record, rid);
    assert(rc == success && "Inserting a record should not fail.");
    assert(rid.slotNum == rid0.slotNum && "Inserted record should use previous deleted slot.");

    // Given the rid, read the record from file
    rc = rbfm.readRecord(fileHandle, recordDescriptor, rid, returnedData);
    assert(rc == success && "Reading a record should not fail.");

    std::cout << std::endl << "Returned Data:" << std::endl;
    rbfm.printRecord(recordDescriptor, returnedData);

    // Compare whether the two memory blocks are the same
    if (memcmp(record, returnedData, recordSize) != 0) {
        std::cout << "[FAIL] Test Case Delete Failed!" << std::endl << std::endl;
        free(record);
        free(returnedData);
        free(nullsIndicator);
        return -1;
    }

    std::cout << std::endl;

    // Close the file
    rc = rbfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    // Destroy the file
    rc = rbfm.destroyFile(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    rc = destroyFileShouldSucceed(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    free(record);
    free(returnedData);
    free(nullsIndicator);

    std::cout << "RBF Test Case Delete Finished! The result will be examined." << std::endl << std::endl;

    return 0;
}

void *record = malloc(2000);
void *returnedData = malloc(2000);
std::vector<Attribute> recordDescriptor;
unsigned char *nullsIndicator = NULL;
FileHandle fileHandle;

void readRecord(RecordBasedFileManager &rbfm, const RID &rid, const std::string &str) {
    int recordSize;
    prepareRecord(recordDescriptor.size(), nullsIndicator, str.length(), str, 25, 177.8, 6200,
                  record, &recordSize);

    RC rc = rbfm.readRecord(fileHandle, recordDescriptor, rid, returnedData);
    assert(rc == success && "Reading a record should not fail.");

    // Compare whether the two memory blocks are the same
    assert(memcmp(record, returnedData, recordSize) == 0 && "Returned Data should be the same");
}

void insertRecord(RecordBasedFileManager &rbfm, RID &rid, const std::string &str) {
    int recordSize;
    prepareRecord(recordDescriptor.size(), nullsIndicator, str.length(), str, 25, 177.8, 6200,
                  record, &recordSize);

    RC rc = rbfm.insertRecord(fileHandle, recordDescriptor, record, rid);
    assert(rc == success && "Inserting a record should not fail.");

}

void updateRecord(RecordBasedFileManager &rbfm, RID &rid, const std::string& str) {
    int recordSize;
    prepareRecord(recordDescriptor.size(), nullsIndicator, str.length(), str, 25, 177.8, 6200,
                  record, &recordSize);

    RC rc = rbfm.updateRecord(fileHandle, recordDescriptor, record, rid);
    assert(rc == success && "Updating a record should not fail.");

}

int RBFTest_Update(RecordBasedFileManager &rbfm) {
    // Functions tested
    // 1. Create Record-Based File
    // 2. Open Record-Based File
    // 3. Insert Record
    // 4. Read Record
    // 5. Close Record-Based File
    // 6. Destroy Record-Based File
    std::cout << std::endl << "***** In RBF Test Case Update *****" << std::endl;

    RC rc;
    std::string fileName = "test_update";

    // Create a file
    rc = rbfm.createFile(fileName);
    assert(rc == success && "Creating the file should not fail.");

    rc = createFileShouldSucceed(fileName);
    assert(rc == success && "Creating the file should not fail.");

    // Open the file
    rc = rbfm.openFile(fileName, fileHandle);
    assert(rc == success && "Opening the file should not fail.");

    RID rid;
    createRecordDescriptor(recordDescriptor);
    recordDescriptor[0].length = (AttrLength) 1000;

    std::string longStr;
    for (int i = 0; i < 1000; i++) {
        longStr.push_back('a');
    }

    std::string shortStr;
    for (int i = 0; i < 10; i++) {
        shortStr.push_back('s');
    }

    std::string midString;
    for (int i = 0; i < 100; i++) {
        midString.push_back('m');
    }

    // Initialize a NULL field indicator
    int nullFieldsIndicatorActualSize = getActualByteForNullsIndicator(recordDescriptor.size());
    nullsIndicator = (unsigned char *) malloc(nullFieldsIndicatorActualSize);
    memset(nullsIndicator, 0, nullFieldsIndicatorActualSize);

    // Insert short record
    insertRecord(rbfm, rid, shortStr);
    RID shortRID = rid;

    // Insert mid record
    insertRecord(rbfm, rid, midString);
    RID midRID = rid;

    // Insert long record
    insertRecord(rbfm, rid, longStr);

    // update short record
    updateRecord(rbfm, shortRID, midString);

    //read updated short record and verify its content
    readRecord(rbfm, shortRID, midString);

    // insert two more records
    insertRecord(rbfm, rid, longStr);
    insertRecord(rbfm, rid, longStr);

    // read mid record and verify its content
    readRecord(rbfm, midRID, midString);

    // update short record
    updateRecord(rbfm, shortRID, longStr);

    // read the short record and verify its content
    readRecord(rbfm, shortRID, longStr);

    // delete the short record
    rbfm.deleteRecord(fileHandle, recordDescriptor, shortRID);

    // verify the short record has been deleted
    rc = rbfm.readRecord(fileHandle, recordDescriptor, shortRID, returnedData);

    assert(rc != success && "Read a deleted record should not success.");

    rc = rbfm.closeFile(fileHandle);
    assert(rc == success && "Closing the file should not fail.");

    // Destroy the file
    rc = rbfm.destroyFile(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    rc = destroyFileShouldSucceed(fileName);
    assert(rc == success && "Destroying the file should not fail.");

    free(record);
    free(returnedData);
    free(nullsIndicator);

    std::cout << "RBF Test Case Update Finished! The result will be examined." << std::endl << std::endl;

    return 0;
}


int main() {
    // To test the functionality of the paged file manager
    PagedFileManager &pfm = PagedFileManager::instance();
    RecordBasedFileManager &rbfm = RecordBasedFileManager::instance();

    // Remove files that might be created by previous test run
    remove("test1");
    remove("test1&freeList");
    RBFTest_1(pfm);

    RBFTest_2(pfm);

    remove("test3");
    remove("test3&freeList");
    RBFTest_3(pfm);

    RBFTest_4(pfm);

    RBFTest_5(pfm);

    RBFTest_6(pfm);

    remove("test7");
    remove("test7&freeList");
    RBFTest_7(pfm);

    remove("test8");
    remove("test8&freeList");
    RBFTest_8(rbfm);

    remove("test8b");
    remove("test8b&freeList");
    RBFTest_8b(rbfm);

    remove("test9");
    remove("test9&freeList");
    remove("test9rids");
    remove("test9sizes");
    std::vector<RID> rids;
    std::vector<int> sizes;
    RBFTest_9(rbfm, rids, sizes);

    RBFTest_10(rbfm);

    remove("test11");
    remove("test11&freeList");
    remove("test11rids");
    RBFTest_11(rbfm);

    RBFTest_12(rbfm);

    remove("test_delete");
    remove("test_delete&freeList");
    RBFTest_Delete(RecordBasedFileManager::instance());

    remove("test_update");
    remove("test_update&freeList");
    RBFTest_Update(RecordBasedFileManager::instance());

    return 0;
}